/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1/commercial
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may use this file in compliance with the License.
 * Alternatively, if you can't copy with the License, namely if you do not
 * want to provide source code of your modifications to this file, you may
 * negotiate a commercial licence with the initial developer.
 * You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is COID.
 *
 * The Initial Developer of the Original Code is
 * PosAm.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Brano Kemen
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "coid/comm/binstream/nullstream.h"

#include "coid/svc/coidsvc.h"

#include "serviceinst.h"
#include "server.h"


namespace coid {


//comm_mutex ServiceInstanceCoid::_ifclist_mutex( true, "static ServiceInstanceCoid::_ifclist_mutex" );
//keywordmap<void*,ServiceInstanceCoid*,ServiceInstanceCoid::hash_voidp>  ServiceInstanceCoid::_ifclist;



////////////////////////////////////////////////////////////////////////////////
opcd ServiceInstanceCoid::disconnect_interface_catch()
{
    // this method receives a fake this ptr that actually is a registered interface
    ServiceInstanceCoid* svci = find_iface_inst(this);
    if(svci)
        return svci->disconnect_interface(this);
    
    return ersNOT_FOUND "no such registered interface";
}

////////////////////////////////////////////////////////////////////////////////
opcd ServiceInstanceCoid::connect_interface( void* pifc )
{
    //GUARDME_REG;

    if( !insert_iface_inst( pifc ) )
        return ersALREADY_EXISTS;

    return 0;
}

////////////////////////////////////////////////////////////////////////////////
opcd ServiceInstanceCoid::disconnect_interface( void* pifc )
{
    RASSERT( erase_iface_inst(pifc) );
    GUARDME_REG;

    if( owning_itself()  &&  !is_autoinstance()  &&  _mutex.counter_fast() <= 2 )
        request_detach();
    return 0;
}

////////////////////////////////////////////////////////////////////////////////
opcd ServiceInstanceCoid::dispatch( uint method, binstream& bin, metastream* meta )
{
    if( method == StdDispatchMethod::ECHO )
        return CoidNode::dispatch( method, bin, meta );
    if( method == StdDispatchMethod::PREDESTRUCTOR )
    {
        if( _flags & fAUTOINSTANCE )
        {
            //lower the artifically incremented lock count
            GUARDME_REG;
            _mutex.dec_fast();
        }
    }

    InstanceData ida;
    ida._cmdin = &_cmdin;
    //ida._hptimer = &SINGLETON(HPTIMER);
    ida._object = _instptr;
    ida._flags = _desc->get_flags(method) & _flags;

    //if( (method & 0xffff0000)  ==  0x80000000 )
    //    ida._flags |= ServiceDescriptor::MethodDesc::fLOG_CALL;

    opcd e;
    if( (method & StdDispatchMethod::fNO_LOCK) || _desc->is_unlocked(method) )
    {
        //proceed here because we don't want locking for some methods
        e = _desc->dispatch( &ida, method, bin, meta );
    }
    else
    {
        GUARD g( *this, _desc->is_const(method) != 0 );
        e = _desc->dispatch( &ida, method, bin, meta );
    }

    _instptr = ida._object;
    return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd ServiceInstanceCoid::dispatch_cmd( const token& cmd, command_tokens& arg, txtstream& reply )
{
    if( cmd == "log" )
    {
        _flags ^= xLOG;

        if( _flags & fLOG_CALL )
            reply << "method log enabled";
        else
            reply << "method log disabled";
        return 0;
    }
    else
        return CoidNode::dispatch_cmd( cmd, arg, reply );
}

////////////////////////////////////////////////////////////////////////////////
opcd ServiceInstanceCoid::dispatch_call( binstream& bin, uint method, metastream* meta )
{
    if( method & 0x80000000 )
    {
        GUARDME_REG;

        LNID k;

        switch (method) {
        case StdProtocolMethod::PING:
            {
                bin.acknowledge();
                bin << opcd(0);
                bin.flush();
                
                return 0;
            }
/*
        case StdProtocolMethod::DISCONNECT_AND_WAIT:
            {
                uint timeout;
                bin >> timeout;
                _timetoexit = timeout / PACKET_TIMESLICE;
                bin.acknowledge();
                bin << opcd(0);
                bin.flush();

                post_trace(1) << "disconnecting bcon object with timeout " << timeout << "ms" << EOM;

                _bin.destroy();
                _flags |= fEXITWAIT;
                return 0;//1;
            }
*/
        case StdProtocolMethod::DISCONNECT:
            {
                bin.acknowledge();
                if(meta) meta->stream_out(opcd(0),"err"); else bin << opcd(0);
                bin.flush();

				if( owning_itself() )
					request_detach();

                return 0;
            }

        case StdProtocolMethod::GET_INSTANCE_ADDRESS:
            {
                bin.acknowledge();
                netaddr oa;  uint objid;
                opcd e = get_object_address(oa,&objid);
                bin << e;
                if(!e)
                    bin << oa << objid;
                bin.flush();
                break;
            }

        case StdDispatchMethod::SPAWN:
            {
                bin.acknowledge();

                int respawn = 0;
                if( (_flags & ServiceInstanceCoid::fALLOW_SHARED)
                    && !(_flags & ServiceInstanceCoid::fAUTOINSTANCE) )
                {
                    post_info(1) << "cannot spawn, shared connections already permitted"
                        << class_name() << ")" << EOM;
                    break;
                }

                try
                {
                    dispatch( method, bin, meta );
                }
                catch( thread::Exception& )   { throw; }
                catch( opcd e )
                {
                    post_critical(0) << "exception in main thread: "
                        << opcd_formatter(e) << ", respawning" << EOM;
                    ++respawn;
                }
                catch(...)
                {
                    post_critical(0) << "exception in main thread, respawning" << EOM;
                    ++respawn;
                }

                //if(respawn)
                //    SRVROOTA.ask_run_update_plglist();

                if( _flags & ServiceInstanceCoid::fAUTOINSTANCE )
                {
                    post_info(1) << "autospawned service returned from its thread, terminating the autospawn instance ("
                        << class_name() << ")" << EOM;
                    return ersEXIT;
                }
                break;
            }
/*
        case StdProtocolMethod::SET_CONN_TIMEOUT:
            {
                bin >> _timeout;
                bin.acknowledge();
                bin << opcd(0);
                bin.flush();
            }
            break;
*/
        default:
            {
                bin >> BINSTREAM_ACK_EAT;
                bin << ersFE_UNKNOWN_METHOD;
                bin.flush();
                post_error(0) << "unrecognized system event request: " << method << EOM;
            }
        }
    }
    else if( _flags & ServiceInstanceCoid::fAUTHENTIFIED )
    {
        if( 0 == _desc->is_implemented(method) )
        {
            bin >> BINSTREAM_ACK_EAT;
            bin << ersFE_UNKNOWN_METHOD;
            bin.flush();
            post_error(0) << "unknown method call: " << method << EOM;
        }
        else
        {
            //GUARD g( *this, _desc->is_const(method) != 0 );

            dispatch( method, bin, meta );
        }
    }
    else {
        bin >> BINSTREAM_ACK_EAT;
        bin << ersFE_NOT_AUTHENTIFIED;
        bin.flush();
        post_error(0) << "not authentified, querying method: " << method << EOM;
    }

    return 0;
}

////////////////////////////////////////////////////////////////////////////////
opcd ServiceInstanceCoid::list_cmds( const token& cmd, dynarray<charstr>& cmds ) const
{
    const ServiceDescriptor::MethodDesc* mout = _desc->_methflags;

    if( cmd.is_empty() )
    {
        //dump all
        for( uint i=0; i<_desc->_nmeth; ++i )
        {
            charstr& m = *cmds.add(1);
            m << "." << mout[i].name;
        }

        cmds.push("log");

        return CoidNode::list_cmds( cmd, cmds );
    }
    else
    {
        token tmd = cmd;
        if( tmd[0] == '.' )
            ++tmd;
        else
        {
            if( cmd == "log" )
                cmds.push("log\n\tenable or disable method logging");
            else
                return CoidNode::list_cmds( cmd, cmds );
            return 0;
        }

        uint fm=0;
        for( uint i=0; i<_desc->_nmeth; ++i )
        {
            if( tmd != mout[i].name )
                continue;

            ++fm;

            charstr& m = *cmds.add(1);
            m << "." << mout[i].name;
            if( mout[i].flags & ServiceDescriptor::MethodDesc::fCONST )
                m << " [const]";

            if( mout[i].inargs.size() )
            {
                m << " IN:( ";
                uints n = mout[i].inargs.size() - 1;
                uints j;
                for( j=0; j<n; ++j )
                {
                    m << mout[i].inargs[j].type << mout[i].inargs[j].name;
                    m << ", ";
                }
                m << mout[i].inargs[j].type << mout[i].inargs[j].name << " )";
            }

            if( mout[i].outargs.size() )
            {
                m << " OUT:( ";
                uints n = mout[i].outargs.size() - 1;
                uints j;
                for( j=0; j<n; ++j )
                {
                    m << mout[i].outargs[j].type << mout[i].outargs[j].name;
                    m << ", ";
                }
                m << mout[i].outargs[j].type << mout[i].outargs[j].name << " )";
            }
        }

        return !fm ? ersNOT_FOUND : opcd(0);
    }
}


} // namespace coid
